/*
 This file is part of BTStracker
		
    BTStracker is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    BTStracker is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Foobar.  If not, see <http://www.gnu.org/licenses/>.	
	author: azbest.pro (azbest.pro@gmail.com)
*/
package pl.vdl.azbest.mremote;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Observer;
import java.util.TooManyListenersException;
import java.util.logging.Logger;

import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import gnu.io.UnsupportedCommOperationException;

import com.sun.org.apache.bcel.internal.generic.LSTORE;

import static pl.vdl.azbest.mremote.com.CommState.*;
import pl.vdl.azbest.annotations.InProgress;
import pl.vdl.azbest.annotations.Level;
import pl.vdl.azbest.annotations.TODO;
import pl.vdl.azbest.log.LoggingTheGathering;
import pl.vdl.azbest.mremote.com.CommObserver;
import pl.vdl.azbest.mremote.com.CommState;
import pl.vdl.azbest.mremote.com.CommSubject;
import pl.vdl.azbest.mremote.gui.GUIFacade;

@TODO(dateAssigned = "21-03-2008", level = Level.CRITICAL, target = "<br>"
		+ "<strike>1. Listing comm ports</strike><br>"
		+ "<strike>2. Opening and closing port</strike><br>"
		+ "3. Raw raeding and writing to ports<br>"
		+ "4. Moving additional configuration options to Conf <br>"
		+ "5. Building SWT Composites for comm communication"
		+ "<strike>6. Connecting to port in separate thread</strike><br>"
		+ "<strike>7. Moving quering machine for ports from Conf to here </strike><br>"
		+ "8. <strike>Create Observer and Subject for this class</strike> <br>"
		+ "<br>", done = "1,2,")
/** All communication with serial port */
public class CommComunicator implements CommSubject {
	private static CommComunicator cc = new CommComunicator();

	private DataInputStream dis = null;

	private PrintStream ps = null;

	private InputStream is;

	private DataOutputStream dos = null;

	private SerialPort port = null;

	private OutputStream os = null;

	private CommState state = DISCONNECTED;

	private List<CommObserver> olist = new ArrayList<CommObserver>();

	private Logger logger = Logger.getLogger(getClass().getName()); // @jve:decl-index=0:

	private CommComunicator() {
		setState(DISCONNECTED, "Disconnected");
	}
	{
		LoggingTheGathering.addPath(getClass().getName());
	}

	public static CommComunicator getInstance() {
		if (cc != null)
			return cc;
		else {
			cc = new CommComunicator();
			return cc;
		}
	}

	public void connect() {
		logger.info("connecting");
		if (state == CONNECTED)
			disconnect();
		Thread t = new Thread() {
			public void run() {
				openPort();
				
				if (state == CONNECTED) {
					logger.info("we ARE connected");
					CommRedaer cr = new CommRedaer();
					Thread tReading = new Thread(cr);
					tReading.setName(Conf.getInstance().getDevicePort().getName()
							+ "_port_reading_thread");
					reader = tReading;
					tReading.start();
					logger.info("RedingThread: started.");
				}

			}
		};
		t.setName("MCCommPortConnecting");
		t.start();


	}

	public void disconnect() {
		logger.info("Ready to disconnect");
		if (state == DISCONNECTED)
			return;
		Thread t = new Thread() {
			public void run() {
				logger.info("In disconnecting thread ...");
				closePort();
				
			}
		};
		t.setName("MCCommPortDisconnecting");
		t.start();
		logger.info("Disconnecting thread started ...");
		
	}

	private void openPort() {

		try {

			setState(CONNECTING, "Connecting to port, timeout "
					+ Conf.getInstance().getCommConnectionTimeout()
					+ " seconds ...");

			port = (SerialPort) Conf.getInstance().getDevicePort().open(
					Conf.getInstance().getCommPortName(),
					1000 * Conf.getInstance().getCommConnectionTimeout());

			port.setSerialPortParams(Conf.getInstance().getDeviceBaudRate(),
					SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
					SerialPort.PARITY_NONE);//
			/*
			 * accoriding to Conf myPort.setSerialPortParams(BAUD,
			 * SerialPort.DATABITS_8,
			 * 
			 * SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
			 * 
			 */

			// port.notifyOnOutputEmpty(true);
			port.notifyOnDataAvailable(true);
			/*
			 * port.notifyOnBreakInterrupt(true);
			 * port.notifyOnCarrierDetect(true); port.notifyOnCTS(true);
			 * 
			 * port.notifyOnDSR(true); port.notifyOnFramingError(true);
			 * port.notifyOnOutputEmpty(true); port.notifyOnOverrunError(true);
			 * port.notifyOnParityError(true); port.notifyOnRingIndicator(true);
			 */
			is = port.getInputStream();

			os = port.getOutputStream();
			logger.info("Port opended, streams opened");

		} catch (PortInUseException e) {
			setState(CONNECTION_FAILURE, "Connection failure. Port is in use.");
			e.printStackTrace();
		} catch (UnsupportedCommOperationException e) {
			setState(CONNECTION_FAILURE,
					"Connection failure. Unsupported operation");
			e.printStackTrace();
		} catch (IOException e) {
			setState(CONNECTION_FAILURE, "Connection failure. IO error");
			e.printStackTrace();
		} /*
			 * catch (TooManyListenersException e) {
			 * setState(CONNECTION_FAILURE, "Too many listeners exception, see
			 * javax.comm API for details"); e.printStackTrace(); }
			 */
		if (state == CONNECTING) {
			setState(CONNECTED, "Connected to port " + port.getName() + " \""
					+ Conf.getInstance().getCommPortName() + "\" with "
					+ port.getBaudRate() + " bauds \n");
		} else if (state == CONNECTION_FAILURE) {
			setState(DISCONNECTED, "");
		}
	}

	private void closePort() {
		logger.info("closeing ...");
		if (port != null && is != null && os != null) {
			try {
				this.is.close();
				logger.info("--input stream closed.");
				this.os.close();
				logger.info("--output stream closed. reafer state = "+reader.getState().name());
				reader.interrupt();
				logger.info("--reader trhread interrupted.");
				port.close();
				logger.info("--port closed.");
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		setState(DISCONNECTED, "Disconnected.");
	}

	public void writeToComm(String message) {
		if (state == CONNECTED) {
			setState(WRITING, "Writing message: " + message);
			try {
				int i = message.getBytes().length;
				byte[] b = new byte[i];
				os.write(b);
				//os.flush();
			} catch (IOException e) {
				e.printStackTrace();
			}

		} else {
			setState(DISCONNECTED, "Cannot execute command, disconnected");
		}
		setState(CONNECTED, "");

	}

	/**
	 * In order to read from comm, register reading class as CommPort observer.
	 * You get notifications on CommComunicator state.
	 * 
	 * @deprecated
	 */
	public Object readFromComm() {
		return "";
	}

	public void registerObserver(CommObserver o) {
		this.olist.add(o);
	}

	public void removeObserver(CommObserver o) {
		this.olist.remove(o);
	}

	public void notifyObservers() {
		for (CommObserver co : this.olist) {
			co.update(this.state);
		}
	}

	public void setState(CommState state, String txt) {
		state.setGUITxt(txt);
		this.state = state;
		notifyObservers();
	}

	public CommState getState() {
		return this.state;
	}

	private class CommRedaer implements Runnable {
		public void run() {
			logger.info("RedingThread: run");

			try {
				int length = -1;
				byte[] b = new byte[1024];
				logger.info("RedingThread: buffer prepared");
				while ((length = is.read(b))> -1) {
					logger.info("RedingThread: in loop, READING"+new String(b, 0, length));
					setState(READING, new String(b, 0, length));
				}
				logger.info("RedingThread: end of runnable - done with reading");

			} catch (IOException e) {
				setState(READING_FAILURE, Conf.getInstance().getDevicePort()
						.getName()
						+ " Port read error. IO Exception ");
				
				e.printStackTrace();
			}

		}
	}
	Thread reader ;

}
